Allow plugins to have native dylib dependencies
authorAlex Crichton <alex@alexcrichton.com>
Sun, 21 Sep 2014 22:15:00 +0000 (15:15 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Sun, 21 Sep 2014 22:15:36 +0000 (15:15 -0700)
Ensure that the dynamic linker search path contains the location of the output
directories for these dependencies when compiling with plugins.

Closes #597

src/cargo/ops/cargo_rustc/mod.rs
tests/test_cargo_compile_plugins.rs

index ea59bab113efaba21a9fd5d4dfa9fd8333bcaf22..4fe6659e115fa861537873fc9950beb9088c2838 100644 (file)
@@ -412,7 +412,15 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package,
 
     // Traverse the entire dependency graph looking for -L paths to pass for
     // native dependencies.
-    cmd = push_native_dirs(cmd, &layout, package, cx, &mut HashSet::new());
+    let mut dirs = Vec::new();
+    each_dep(package, cx, |pkg| {
+        if pkg.get_manifest().get_build().len() > 0 {
+            dirs.push(layout.native(pkg));
+        }
+    });
+    for dir in dirs.into_iter() {
+        cmd = cmd.arg("-L").arg(dir);
+    }
 
     for &(_, target) in cx.dep_targets(package).iter() {
         cmd = try!(link_to(cmd, target, cx, kind, Dependency));
@@ -460,37 +468,51 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package,
         }
         return Ok(cmd);
     }
-
-    fn push_native_dirs(mut cmd: ProcessBuilder, layout: &layout::LayoutProxy,
-                        pkg: &Package, cx: &Context,
-                        visited: &mut HashSet<PackageId>) -> ProcessBuilder {
-        if !visited.insert(pkg.get_package_id().clone()) { return cmd }
-
-        if pkg.get_manifest().get_build().len() > 0 {
-            cmd = cmd.arg("-L").arg(layout.native(pkg));
-        }
-
-        match cx.resolve.deps(pkg.get_package_id()) {
-            Some(mut pkgids) => {
-                pkgids.fold(cmd, |cmd, dep_id| {
-                    let dep = cx.get_package(dep_id);
-                    push_native_dirs(cmd, layout, dep, cx, visited)
-                })
-            }
-            None => cmd
-        }
-    }
 }
 
 pub fn process<T: ToCStr>(cmd: T, pkg: &Package, cx: &Context) -> ProcessBuilder {
     // When invoking a tool, we need the *host* deps directory in the dynamic
     // library search path for plugins and such which have dynamic dependencies.
+    let layout = cx.layout(KindPlugin);
     let mut search_path = DynamicLibrary::search_path();
-    search_path.push(cx.layout(KindPlugin).deps().clone());
-    let search_path = os::join_paths(search_path.as_slice()).unwrap();
+    search_path.push(layout.deps().clone());
+
+    // Also be sure to pick up any native build directories required by plugins
+    // or their dependencies
+    let mut native_search_paths = HashSet::new();
+    for &(dep, target) in cx.dep_targets(pkg).iter() {
+        if !target.get_profile().is_plugin() { continue }
+        each_dep(dep, cx, |dep| {
+            if dep.get_manifest().get_build().len() > 0 {
+                native_search_paths.insert(layout.native(dep));
+            }
+        });
+    }
+    search_path.extend(native_search_paths.into_iter());
 
     // We want to use the same environment and such as normal processes, but we
     // want to override the dylib search path with the one we just calculated.
+    let search_path = os::join_paths(search_path.as_slice()).unwrap();
     cx.compilation.process(cmd, pkg)
                   .env(DynamicLibrary::envvar(), Some(search_path.as_slice()))
 }
+
+fn each_dep<'a>(pkg: &Package, cx: &'a Context, f: |&'a Package|) {
+    let mut visited = HashSet::new();
+    let pkg = cx.get_package(pkg.get_package_id());
+    visit_deps(pkg, cx, &mut visited, f);
+
+    fn visit_deps<'a>(pkg: &'a Package, cx: &'a Context,
+                      visited: &mut HashSet<&'a PackageId>,
+                      f: |&'a Package|) {
+        if !visited.insert(pkg.get_package_id()) { return }
+        f(pkg);
+        let mut deps = match cx.resolve.deps(pkg.get_package_id()) {
+            Some(deps) => deps,
+            None => return,
+        };
+        for dep_id in deps {
+            visit_deps(cx.get_package(dep_id), cx, visited, |p| f(p))
+        }
+    }
+}
index 0d4f94322a2d65224be99c5286842be668875584..b59c8df3411d9126b01fc219d1d2d317c98a57fb 100644 (file)
@@ -1,3 +1,6 @@
+use std::io::fs;
+use std::os;
+
 use support::{project, execs, cargo_dir};
 use hamcrest::assert_that;
 
@@ -78,3 +81,89 @@ test!(plugin_to_the_max {
     assert_that(foo.process(cargo_dir().join("cargo")).arg("doc"),
                 execs().with_status(0));
 })
+
+test!(plugin_with_dynamic_native_dependency {
+    let build = project("build")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "build"
+            version = "0.0.1"
+            authors = []
+
+            [lib]
+            name = "build"
+            crate-type = ["dylib"]
+        "#)
+        .file("src/main.rs", r#"
+            use std::io::fs;
+            use std::os;
+
+            fn main() {
+                let src = Path::new(os::getenv("SRC").unwrap());
+                let dst = Path::new(os::getenv("OUT_DIR").unwrap());
+                let dst = dst.join(src.filename().unwrap());
+                fs::rename(&src, &dst).unwrap();
+            }
+        "#)
+        .file("src/lib.rs", r#"
+            #[no_mangle]
+            pub extern fn foo() {}
+        "#);
+    assert_that(build.cargo_process("build"),
+                execs().with_status(0).with_stderr(""));
+    let src = build.root().join("target");
+    let lib = fs::readdir(&src).unwrap().into_iter().find(|lib| {
+        let lib = lib.filename_str().unwrap();
+        lib.starts_with(os::consts::DLL_PREFIX) &&
+            lib.ends_with(os::consts::DLL_SUFFIX)
+    }).unwrap();
+    let libname = lib.filename_str().unwrap();
+    let libname = libname.slice(os::consts::DLL_PREFIX.len(),
+                                libname.len() - os::consts::DLL_SUFFIX.len());
+
+    let foo = project("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs", r#"
+            #![feature(phase)]
+            #[phase(plugin)] extern crate bar;
+
+            fn main() {}
+        "#)
+        .file("bar/Cargo.toml", format!(r#"
+            [package]
+            name = "bar"
+            version = "0.0.1"
+            authors = []
+            build = '{}'
+
+            [lib]
+            name = "bar"
+            plugin = true
+        "#, build.bin("build").display()))
+        .file("bar/src/lib.rs", format!(r#"
+            #![feature(plugin_registrar)]
+
+            extern crate rustc;
+
+            use rustc::plugin::Registry;
+
+            #[link(name = "{}")]
+            extern {{ fn foo(); }}
+
+            #[plugin_registrar]
+            pub fn bar(_reg: &mut Registry) {{
+                unsafe {{ foo() }}
+            }}
+        "#, libname));
+
+    assert_that(foo.cargo_process("build").env("SRC", Some(lib.as_vec())),
+                execs().with_status(0));
+})